#ifndef     __CMD_UART_H
#define     __CMD_UART_H

#include "../utils/object.h"
#include "../utils/nocopyable.h"
#include "../rtos/message_queue.h"
#include "../utils/byte_view.h"
#include "../utils/encode.h"
#include "../rtos/event_flags.h"
#include "../conn/proto.h"

namespace driver {

class CmdUart : private utils::NoCopyable {

public:
    static constexpr uint32_t RECV_BUF_SIZE = 128;
    static constexpr uint32_t WRITE_BUF_SIZE = 64;

    static constexpr uint32_t FLAG_WRITE_COMPLETE = 0x01;
    static constexpr uint32_t FLAG_ACK = 0x02;
    static constexpr uint32_t FLAG_WRITE_WAIT_ACK = 0x04;

    CmdUart();

    void write_raw(const void *buf, uint8_t len);

    inline bool poll(utils::ByteView &bw, uint32_t timeout = osWaitForever) {
        return osOK == m_mq.poll(bw, timeout);
    }

    void write_ack(uint8_t seq);

    void log(const char *format, ...);

    template <typename... Ts>
    void write_notify(uint8_t cmd, Ts&&... ts) {
		using namespace utils;
        wait_write_complete();
        uint8_t data_len = encode_all(m_write_buf + 5, std::forward<Ts>(ts)...);
        uint8_t len = conn::proto::make_notify(m_write_buf, cmd, data_len);
        write_by_dma(m_write_buf, len);
    }

    template <typename... Ts>
    void write_result(uint8_t cmd, Ts&&... ts) {
		using namespace utils;
        wait_write_complete();
        uint8_t data_len = encode_all(m_write_buf + 5, std::forward<Ts>(ts)...);
        uint8_t len = conn::proto::make_result(m_write_buf, cmd, data_len);
        write_by_dma(m_write_buf, len);
    }

    template <typename... Ts>
    utils::Error write_resp(uint8_t seq, uint8_t cmd, Ts&&... ts) {
        using namespace utils;
        Error err;
        wait_ack_lock();
        clear_ack();
        for (uint8_t i = 0; i < 3; i ++) {
            wait_write_complete();
            uint8_t data_len = encode_all(m_write_buf + 6, std::forward<Ts>(ts)...);
            uint8_t len = conn::proto::make_res(m_write_buf, seq, cmd, data_len);
            write_by_dma(m_write_buf, len);
            if (!wait_ack()) {
                err = ec::NOT_RECV_ACK;
                log("not recv ack");
            }
            if (err.is_ok()) {
                break;
            }
        }
        wait_ack_unlock();
        return err;
    }

    inline void on_ack() { m_flags.set_flags(FLAG_ACK); }

    void notify_write_complete() { m_flags.set_flags(FLAG_WRITE_COMPLETE); }
    void on_recv_idle();

private:    
    void wait_write_complete() { m_flags.wait_any_flags(FLAG_WRITE_COMPLETE); }
   
    static void write_by_dma(const void *buf, uint8_t len);

    inline void clear_ack() {
        m_flags.clr_flags(FLAG_ACK);
    }

    inline bool wait_ack() {
        return 0x00 == (osFlagsError & m_flags.wait_any_flags(FLAG_ACK, 500));
    }

    inline void wait_ack_lock() {
        m_flags.wait_any_flags(FLAG_WRITE_WAIT_ACK);
    }

    inline void wait_ack_unlock() {
        m_flags.set_flags(FLAG_WRITE_WAIT_ACK);
    }

    uint8_t m_recv_buf[RECV_BUF_SIZE];
    uint8_t m_write_buf[WRITE_BUF_SIZE];
    uint32_t m_recv_index = 0;
    rtos::MessageQueue<utils::ByteView, 5> m_mq;
    rtos::EventFlags m_flags;
};

inline utils::Object<CmdUart> cmdUart;

}



#endif
